#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import locale
import os
import psutil
from oracle import logger
from oracle.common.constants import PluginPathConstant, ScriptExitCode, SliceConstant, RETURN_INVALID
from oracle.common.windows_common import write_tmp_file, set_db_silence_sql, execute_windows_sqlplus_cmd, \
    delete_file, read_result_file, execute_windows_cmd, get_database_info
from oracle.services.resource.oracle_windows_resource import get_asm_instance_name


class OracleWindowsCheckArchive:
    def __init__(self, pid, instance_name, db_user="", db_password=""):
        self.pid = pid
        self.db_user = db_user
        self.db_password = db_password
        self.instance_name = instance_name
        self.login_params = {
            "pid": pid,
            "db_user": db_user,
            "db_password": db_password,
            "instance_name": instance_name
        }
        self.sqlplus_params = {
            "is_silence": 1,
            "db_user": db_user,
            "db_password": db_password
        }

    def oracle_windows_check_archive(self, oracle_home, db_archive_limen):
        ret_code = self.check_db_invalid()
        if ret_code != ScriptExitCode.SUCCESS:
            return ScriptExitCode.SUCCESS

        # check archive mode & archive using
        if db_archive_limen != 0:
            ret_code = self.check_archive_mode_and_archive_using(oracle_home, db_archive_limen)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Execute check archive mode and archive using FAILED! "
                             f"Pid = {self.pid}, ret_code = {ret_code}")
                return ret_code
            logger.info(f"Archive free space is higher than the threshold! Pid = {self.pid}")
            return ret_code
        else:
            logger.info(f"db_archive_limen is zero, not to check archive using! pid = {self.pid}")
            return ScriptExitCode.SUCCESS

    def check_archive_mode_and_archive_using(self, oracle_home, db_archive_limen):
        logger.debug("Begin check archive dest dierectory")
        pid = self.pid
        instance_name = self.instance_name

        archive_dest_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/ArchiveDestSQL{pid}.sql"
        archive_dest_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/ArchiveDestRST{pid}.txt"
        archive_dest_list = []

        ret_code = self.get_destination_from_archive_dest(archive_dest_sql, archive_dest_rst)
        if ret_code != ScriptExitCode.SUCCESS:
            return ret_code

        encoding = locale.getdefaultlocale()[1]
        result_content = read_result_file(archive_dest_rst, encoding=encoding)
        for line in result_content.splitlines():
            if (line[1]) == ":" or line.startswith("+", SliceConstant.HEAD_OF_STRING_OR_LIST) \
                    or ("USE_DB_RECOVERY_FILE_DEST" in line):
                archive_dest_list.append(line)
        delete_file(archive_dest_sql)
        delete_file(archive_dest_rst)

        for line in archive_dest_list:
            str_archive_dest = line.strip()
            fast_used_capacity = 0
            fast_all_capacity = 0

            # default dest, need to search directory
            if str_archive_dest == "USE_DB_RECOVERY_FILE_DEST":
                [ret_code, str_archive_dest, fast_all_capacity, fast_used_capacity] = \
                    self.calculate_fast_capacity_under_default_destination(
                        pid, archive_dest_sql, archive_dest_rst, instance_name)
                if ret_code != ScriptExitCode.SUCCESS:
                    return ret_code
            else:
                str_archive_dest = self.modify_archive_destination(str_archive_dest)

            logger.debug(f"str_archive_dest = {str_archive_dest}")
            if str_archive_dest[0:12] == "/dev/raw/raw":
                logger.info(f"Archive dest {str_archive_dest} is the raw, not support check archive dest size")
                continue

            ret_code, all_capacity, free_capacity = self.calculate_capacity(pid, oracle_home, str_archive_dest,
                                                                            archive_dest_list)
            if ret_code != ScriptExitCode.SUCCESS:
                return ret_code

            used = all_capacity - free_capacity
            used_rate = round(100 * used / all_capacity, 2)
            if float(fast_all_capacity) > 0 and float(fast_used_capacity) > 0:
                used_rate_fast = round(100 * float(fast_used_capacity) / float(fast_all_capacity), 2)
                if used_rate_fast > used_rate:
                    used_rate = used_rate_fast

            if used_rate >= int(db_archive_limen):
                logger.info(f"archive dest({str_archive_dest}) used_rate({used_rate}%) is larger than the threshold")
                return ScriptExitCode.ERROR_ORACLE_OVER_ARCHIVE_USING

        delete_file(archive_dest_sql)
        delete_file(archive_dest_rst)
        logger.debug(f"Success check archive dest directory using!")
        return ScriptExitCode.SUCCESS

    def get_destination_from_archive_dest(self, archive_dest_sql, archive_dest_rst):
        pid = self.login_params.get("pid", "")
        db_user = self.login_params.get("db_user", "")
        db_password = self.login_params.get("db_password", "")
        instance_name = self.login_params.get("instance_name", "")

        content = f"set linesize 300;\n" \
                  f"col DESTINATION for a255;\n" \
                  f"SPOOL {archive_dest_rst}\n" \
                  f"select DESTINATION from v$archive_dest where STATUS='VALID';\n" \
                  f"SPOOL OFF\n" \
                  f"exit;"
        write_tmp_file(archive_dest_sql, content)
        set_db_silence_sql(archive_dest_sql)

        params = {
            "is_silence": 1,
            "db_user": db_user,
            "db_password": db_password
        }
        ret_code = execute_windows_sqlplus_cmd(pid, params, archive_dest_sql, archive_dest_rst, instance_name)
        if ret_code != ScriptExitCode.SUCCESS:
            delete_file(archive_dest_sql)
            delete_file(archive_dest_rst)
            logger.error(f"Get Archive log dest list failed. Pid = {pid}")
            return ret_code
        return ScriptExitCode.SUCCESS

    def calculate_fast_capacity_under_default_destination(self, pid, archive_dest_sql, archive_dest_rst, instance_name):
        # get archive dest
        content = f"set linesize 300;\n" \
                  f"col NAME for a255;\n" \
                  f"SPOOL {archive_dest_rst}\n" \
                  f"select NAME from v$RECOVERY_FILE_DEST;\n" \
                  f"SPOOL OFF\n" \
                  f"exit;"
        write_tmp_file(archive_dest_sql, content)

        logger.debug(f"Exec SQL to get name of archive dest. Pid = {pid}")
        ret_code = execute_windows_sqlplus_cmd(pid, self.sqlplus_params, archive_dest_sql, archive_dest_rst,
                                               instance_name)
        encoding = locale.getdefaultlocale()[1]
        if ret_code != ScriptExitCode.SUCCESS:
            delete_file(archive_dest_sql)
            delete_file(archive_dest_rst)
            logger.info(f"Get Archive dest FAILED. Pid = {pid}")
            return [ret_code, "", 0, 0]
        str_archive_dest = read_result_file(archive_dest_rst, encoding=encoding).strip().splitlines()[2]
        delete_file(archive_dest_sql)
        delete_file(archive_dest_rst)

        # get archive using
        content = f"set linesize 100;\n" \
                  f"SPOOL {archive_dest_rst}\n" \
                  f"select SPACE_LIMIT/1024/1024, SPACE_USED/1024/1024 from v$RECOVERY_FILE_DEST;\n" \
                  f"SPOOL OFF\n" \
                  f"exit;"
        logger.debug(f"Exec SQL to get using of archive dest. Pid = {pid}")
        write_tmp_file(archive_dest_sql, content)
        ret_code = execute_windows_sqlplus_cmd(pid, self.sqlplus_params, archive_dest_sql, archive_dest_rst,
                                               instance_name)
        if ret_code != ScriptExitCode.SUCCESS:
            delete_file(archive_dest_sql)
            delete_file(archive_dest_rst)
            logger.info(f"Get Archive dest using FAILED! Pid = {pid}")
            return [ret_code, "", 0, 0]
        tmp_content = read_result_file(archive_dest_rst, encoding=encoding).strip().splitlines()
        tmp_line = [tmp_content[-1].strip()]
        for item in tmp_line:
            fast_all_capacity = item.split()[0]
            fast_used_capacity = item.split()[1]
            # deal number pot
            fast_all_capacity = fast_all_capacity.split(".")[0]
            fast_used_capacity = fast_used_capacity.split(".")[0]
            # first value
            break

        delete_file(archive_dest_sql)
        delete_file(archive_dest_rst)
        logger.info(f"fast_all_capacity = {fast_all_capacity}; fast_used_capacity = {fast_used_capacity}")
        return [ScriptExitCode.SUCCESS, str_archive_dest, fast_all_capacity, fast_used_capacity]

    def modify_archive_destination(self, str_archive_dest):
        last_char = str_archive_dest[-1]
        if str_archive_dest[0] == "/" and last_char != "/" and (not os.path.isdir(str_archive_dest)):
            file_prefix = str_archive_dest.split("/")[-1]
            len_prefix = len(file_prefix)
            len_all_dest = len(str_archive_dest)

            len_dest = len_all_dest - len_prefix - 1
            str_archive_dest = str_archive_dest[0:len_dest]

        if not os.path.isdir(str_archive_dest):
            logger.info(f"Archive Dest {str_archive_dest} is not exists! Pid = {self.pid}")
        return str_archive_dest

    def calculate_capacity(self, pid, oracle_home, str_archive_dest, archive_dest_list):
        free_capacity = 0
        all_capacity = 0
        # file system
        if str_archive_dest[0] != "+":
            logger.info(f"Checking non-ASM archive area! Pid = {pid}")
            usage = psutil.disk_usage(str_archive_dest[0:3])
            free_capacity = usage.free / (1024 * 1024)
            all_capacity = usage.total / (1024 * 1024)

        # archive dest
        if str_archive_dest[0] == "+":
            logger.debug(f"Checking ASM archive area! Pid = {pid}")
            ret_code, asm_sid_name = get_asm_instance_name(pid)
            if not ret_code:
                logger.error(f"Cannot get the asm instance name! Pid = {pid}")
                return RETURN_INVALID, 0, 0
            asm_sid_name = asm_sid_name[0].split()[0]

            logger.debug(f"Check ASM instance name {asm_sid_name}")
            # check ASM instance status
            ret_code, asm_inst_num = self.get_asm_instance_count()
            if not ret_code:
                logger.error(f"Execute sc query FAILED, pid = {pid}")
                return RETURN_INVALID, 0, 0
            if not asm_inst_num:
                logger.info(f"The ASM instance is not open! Pid = {pid}")
                for item in archive_dest_list:
                    delete_file(item)
                return ScriptExitCode.ERROR_INSTANCE_NOSTART, 0, 0

            ret_code, all_capacity, free_capacity = self.get_asm_usage(oracle_home, asm_sid_name)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Get capacity of asm FAILED! pid = {pid}")
                return RETURN_INVALID, 0, 0
            logger.info(f"ASM capacity: all_capacity = {all_capacity}, free_capacity = {free_capacity}")

        return ScriptExitCode.SUCCESS, all_capacity, free_capacity

    def get_asm_usage(self, oracle_home, oracle_sid):
        pid = self.pid
        os.environ["ORACLE_HOME"] = oracle_home
        os.environ["ORACLE_SID"] = oracle_sid
        os.environ["NLS_LANG"] = "American_America.US7ASCII"
        cmd_list = "cmd /c asmcmd -p".split()
        input_cmd = "lsdg"
        ret_code, result = execute_windows_cmd(pid, cmd_list, input_cmd)
        if not ret_code:
            logger.error(f"Execute asmcmd lsdg FAILED! pid = {pid}")
            return RETURN_INVALID, 0, 0
        all_capacity, free_capacity = result.strip().splitlines()[-2].split()[6:8]
        logger.info(f"The ASM all_capacity = {all_capacity} Mb, free_capacity = {free_capacity} Mb, pid = {pid}")
        return ScriptExitCode.SUCCESS, int(all_capacity), int(free_capacity)

    def get_asm_instance_count(self):
        pid = self.pid
        count = 0
        cmd_list = "cmd /c sc query".split()
        ret_code, output = execute_windows_cmd(pid, cmd_list, "")
        if not ret_code:
            logger.error(f"Execute sc query FAILED! pid = {pid}")
            return False, count

        for item in output.splitlines():
            if "SERVICE_NAME: OracleASMService" in item:
                count += 1
        logger.info(f"Function get_asm_instance_count SUCCESS! pid = {pid}")
        return True, count

    def check_db_invalid(self):
        pid = self.pid
        db_user = self.db_user
        db_password = self.db_password
        instance_name = self.instance_name

        ret_code, db_base_info = get_database_info(pid, instance_name, db_user, db_password)
        if ret_code != ScriptExitCode.SUCCESS or (not db_base_info):
            logger.error(f"Get database base info failed, pid:{pid}")
            return ret_code
        log_mode = db_base_info[0]
        if log_mode != "ARCHIVELOG":
            logger.error(f"Archive Mode = No Archive Mode, check archive mode FAILED! pid = {pid}")
            return ScriptExitCode.ERROR_ORACLE_NOARCHIVE_MODE
        return ScriptExitCode.SUCCESS
